package cn.ccew.common.base.dao;

import static org.hibernate.criterion.Example.create;

import java.math.BigInteger;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import cn.ccew.common.base.model.BaseEntity;
import cn.ccew.common.utils.Page;

@SuppressWarnings("unchecked")
@Repository("baseDao")
public class BaseDaoImpl implements IBaseDao {

	@Autowired
	@Qualifier("sessionFactory")
	private SessionFactory sessionFactory;

	private static final Logger log = LoggerFactory.getLogger(BaseDaoImpl.class);

	public Session getSession() {
		return this.sessionFactory.getCurrentSession();
	}

	@Transactional("txManager")
	public void save(BaseEntity bean, String... dbType) {
		String className = bean.getClass().getName();

		log.debug("saving " + className + " instance");
		try {
			bean.setCreateTime(new Timestamp(System.currentTimeMillis()));
			bean.setLastModifyTime(new Timestamp(System.currentTimeMillis()));
			getSession().save(bean);
			getSession().flush();
			log.debug("save " + className + " successful");
		} catch (HibernateException re) {
			DataAccessException dae = SessionFactoryUtils.convertHibernateAccessException(re);
			log.error("save " + className + " failed", dae);
			throw dae;
		}
	}

	public void delete(BaseEntity bean) {
		String className = bean.getClass().getName();

		log.debug("deleting " + className + " instance");
		try {
			getSession().delete(bean);
			log.debug("delete " + className + " successful");
		} catch (HibernateException re) {
			DataAccessException dae = SessionFactoryUtils.convertHibernateAccessException(re);
			log.error("delete " + className + " failed", dae);
			throw dae;
		}

	}

	public void delete(String id, Class<? extends BaseEntity> clazz) {
		String className = clazz.getName();

		log.debug("deleting " + className + " instance");
		try {
			Object bean = getSession().get(clazz, id);
			getSession().delete(bean);
			log.debug("delete " + className + " successful");
		} catch (HibernateException re) {
			DataAccessException dae = SessionFactoryUtils.convertHibernateAccessException(re);
			log.error("delete " + className + " failed", dae);
			throw dae;
		}
	}

	public <T extends BaseEntity> T findById(String id, Class<T> clazz) {
		String className = clazz.getName();
		T instance = null;
		log.debug("getting " + className + " instance with id: " + id);
		try {
			instance = (T) getSession().get(clazz, id);
		} catch (HibernateException re) {
			DataAccessException dae = SessionFactoryUtils.convertHibernateAccessException(re);
			log.error("get " + className + " failed", dae);
			throw dae;
		}
		return instance;
	}

	public <T extends BaseEntity> List<T> findByExample(T t) {
		return this.findByExample(t, null);
	}

	public <T extends BaseEntity> List<T> findByExample(T t, LinkedHashMap<String, String> orderInfos) {
		String className = t.getClass().getName();

		log.debug("finding " + className + " instance by example");
		try {
			Criteria criteria = getSession().createCriteria(className).add(create(t));

			if (orderInfos != null && orderInfos.size() != 0) {
				for (String propertyName : orderInfos.keySet()) {
					if ("asc".equalsIgnoreCase(orderInfos.get(propertyName))) {
						criteria = criteria.addOrder(Order.asc(propertyName));
					} else {
						criteria = criteria.addOrder(Order.desc(propertyName));
					}
				}
			}

			List<T> results = criteria.list();
			log.debug("find by example successful, result size: " + results.size());
			return results;
		} catch (HibernateException re) {
			DataAccessException dae = SessionFactoryUtils.convertHibernateAccessException(re);
			log.error("find by example failed", dae);
			throw dae;
		}
	}

	public <T extends BaseEntity> List<T> findByProperty(String propertyName, Object value, Class<T> clazz) {
		return this.findByProperty(propertyName, value, clazz, null);
	}

	public <T extends BaseEntity> List<T> findByProperty(String propertyName, Object value, Class<T> clazz, LinkedHashMap<String, String> orderInfos) {

		String className = clazz.getName();

		log.debug("finding " + className + " instance with property: " + propertyName + ", value: " + value);
		try {
			String alias = "bean";
			String queryString = "from " + className + " " + alias + " where " + alias + "." + propertyName + "=? ";
			if (value == null) {
				queryString = "from " + className + " " + alias + " where " + alias + "." + propertyName + " is null ";
			}
			if (orderInfos != null && orderInfos.size() != 0) {
				queryString = queryString + " order by ";
				for (String orderPropertyName : orderInfos.keySet()) {
					queryString = queryString + " " + alias + "." + orderPropertyName + " " + orderInfos.get(orderPropertyName) + ",";
				}
				queryString = queryString.substring(0, queryString.length() - 1);
			}

			Query queryObject = getSession().createQuery(queryString);
			if (value != null) {
				queryObject.setParameter(0, value);
			}
			return queryObject.list();
		} catch (HibernateException re) {
			DataAccessException dae = SessionFactoryUtils.convertHibernateAccessException(re);
			log.error("find by property name failed", dae);
			throw dae;
		}

	}

	public <T extends BaseEntity> List<T> findAll(Class<T> clazz) {

		return findAll(clazz, null);
	}

	public <T extends BaseEntity> List<T> findAll(Class<T> clazz, LinkedHashMap<String, String> orderInfos) {

		String className = clazz.getName();

		log.debug("finding all " + className + " instances");

		try {
			String alias = "bean";
			String queryString = "from " + className + " " + alias;
			if (orderInfos != null && orderInfos.size() != 0) {
				queryString = queryString + " order by ";
				for (String propertyName : orderInfos.keySet()) {
					queryString = queryString + " " + alias + "." + propertyName + " " + orderInfos.get(propertyName) + ",";
				}
				queryString = queryString.substring(0, queryString.length() - 1);
			}
			Query queryObject = getSession().createQuery(queryString);
			return queryObject.list();
		} catch (HibernateException re) {
			DataAccessException dae = SessionFactoryUtils.convertHibernateAccessException(re);
			log.error("find all failed", dae);
			throw dae;
		}
	}

	public <T extends BaseEntity> T merge(T bean, String... dbType) {
		String className = bean.getClass().getName();

		log.debug("merging " + className + " instance");
		try {
			bean.setLastModifyTime(new Timestamp(System.currentTimeMillis()));
			T result = (T) getSession().merge(bean);
			log.debug("merge " + className + " successful");
			return result;
		} catch (HibernateException re) {
			DataAccessException dae = SessionFactoryUtils.convertHibernateAccessException(re);
			log.error("merge " + className + " failed", dae);
			throw dae;
		}
	}

	public static void main(String[] args) {
		Long l = System.currentTimeMillis();
		java.util.Date dt = new Date(l);
		SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
		System.out.println(sdf.format(dt));
	}

	public void attachDirty(BaseEntity bean) {
		String className = bean.getClass().getName();

		log.debug("attaching dirty " + className + " instance");
		try {
			getSession().saveOrUpdate(bean);
			log.debug("attach " + className + " successful");
		} catch (HibernateException re) {
			DataAccessException dae = SessionFactoryUtils.convertHibernateAccessException(re);
			log.error("attach " + className + " failed", dae);
			throw dae;
		}
	}

	@SuppressWarnings("deprecation")
	public void attachClean(BaseEntity bean) {
		String className = bean.getClass().getName();
		log.debug("attaching clean " + className + " instance");
		try {
			getSession().lock(bean, LockMode.NONE);
			log.debug("attach " + className + " successful");
		} catch (HibernateException re) {
			DataAccessException dae = SessionFactoryUtils.convertHibernateAccessException(re);
			log.error("attach " + className + " failed", dae);
			throw dae;
		}
	}

	public boolean isExist(String id, Class<? extends BaseEntity> clazz) {
		String className = clazz.getName();

		log.debug("getting " + className + " instance with id: " + id);
		try {
			return getSession().get(clazz, id) != null;
		} catch (HibernateException re) {
			DataAccessException dae = SessionFactoryUtils.convertHibernateAccessException(re);
			log.error("get " + className + " failed", dae);
			throw dae;
		}
	}

	/**
	 * 分页查询。
	 * 
	 * @param query
	 *            需要分页的查询
	 * @param pageNumber
	 *            当前页码
	 * @param pageSize
	 *            每页显示记录数。
	 */
	public Page findBy(String sql, int pageNumber, int pageSize) {

		SQLQuery query = this.getSession().createSQLQuery(sql);

		String countSql = " select count(*) as C from (" + sql + ") R";
		int totalCount = ((BigInteger) this.getSession().createSQLQuery(countSql).uniqueResult()).intValue();

		if (pageNumber < 1)
			pageNumber = 1;
		List list = null;
		int startIndex = 0;

		startIndex = getStartOfAnyPage(pageNumber, pageSize);
		list = query.setFirstResult(startIndex - 1).setMaxResults(pageSize).list();
		// 当前页记录条数
		int avaCount = list == null ? 0 : list.size();
		return new Page(startIndex, avaCount, totalCount, pageSize, list);
	}

	/**
	 * 分页查询。
	 * 
	 * @param query
	 *            需要分页的查询
	 * @param pageNumber
	 *            当前页码
	 * @param pageSize
	 *            每页显示记录数。
	 */
	public Page findBy(Query query, int pageNumber, int pageSize) {
		// 设置记录集滚动方式以计算总计条数
		ScrollableResults scrollableResults = query.scroll(ScrollMode.SCROLL_SENSITIVE);
		scrollableResults.last();
		// 总计记录条数
		int totalCount = scrollableResults.getRowNumber();

		if (pageNumber < 1)
			pageNumber = 1;
		List list = null;
		int startIndex = 0;

		startIndex = getStartOfAnyPage(pageNumber, pageSize);
		list = query.setFirstResult(startIndex - 1).setMaxResults(pageSize).list();
		// 当前页记录条数
		int avaCount = list == null ? 0 : list.size();
		return new Page(startIndex, avaCount, totalCount + 1, pageSize, list);
	}

	/**
	 * 分页查询。
	 * 
	 * @param criteria
	 *            需要分页的查询
	 * @param pageNumber
	 *            当前页码
	 * @param pageSize
	 *            每页显示记录数。
	 */
	public Page findBy(Criteria criteria, int pageNumber, int pageSize) {
		// 总计记录条数
		int totalCount = findByOfTotalRows(criteria);

		if (pageNumber < 1)
			pageNumber = 1;

		List list = null;
		int startIndex = 0;

		startIndex = Page.getStartOfAnyPage(pageNumber, pageSize);
		list = criteria.setFirstResult(startIndex - 1).setMaxResults(pageSize).list();
		// 当前页记录条数
		int avaCount = list == null ? 0 : list.size();

		return new Page(startIndex, avaCount, totalCount + 1, pageSize, list);
	}

	/**
	 * 分页查询。
	 * 
	 * @param criteria
	 *            需要分页的查询
	 * @param pageNumber
	 *            当前页码
	 * @param pageSize
	 *            每页显示记录数。
	 */
	public Page findPageBy(Criteria criteria, int pageNumber, int pageSize) {
		if (pageNumber < 1)
			pageNumber = 1;
		// 总计记录条数
		int totalCount = findPageByOfTotalRows(criteria);

		List list = null;
		int startIndex = 0;
		// 表示需要导出数据,不需要分页.

		startIndex = getStartOfAnyPage(pageNumber, pageSize);
		log.debug("***********findBy**************");

		// 由数据库分页SQL实现
		list = criteria.setFirstResult(startIndex - 1).setMaxResults(pageSize).list();
		// 当前页记录条数
		int avaCount = list == null ? 0 : list.size();

		log.debug("总计记录条数: " + totalCount);

		return new Page(startIndex, avaCount, totalCount + 1, pageSize, list);
	}

	/**
	 * 支持可滚动结果集 如果数据量大,很影响性能
	 * 
	 * @param criteria
	 * @return
	 */
	protected int findByOfTotalRows(Criteria criteria) {
		// 设置记录集滚动方式以计算总计条数
		ScrollableResults scrollableResults = criteria.scroll(ScrollMode.SCROLL_SENSITIVE);
		scrollableResults.last();
		//
		// 总计记录条数
		return scrollableResults.getRowNumber();

		// return findPageByOfTotalRows(criteria);
	}

	protected int findByOfTotalRows(Query query) {
		// 设置记录集滚动方式以计算总计条数
		ScrollableResults scrollableResults = query.scroll(ScrollMode.SCROLL_SENSITIVE);
		scrollableResults.last();
		// 总计记录条数
		return scrollableResults.getRowNumber();
	}

	/**
	 * 分页查询时获取总的记录条数
	 * 
	 * @param criteria
	 * @return
	 */
	protected int findPageByOfTotalRows(Criteria criteria) {
		int totalCount = ((Integer) criteria.setProjection(Projections.rowCount()).uniqueResult()).intValue();
		criteria.setProjection(null);
		criteria.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
		return totalCount;
	}

	/**
	 * 获取任一页第一条数据在数据库中的位置
	 */
	public static int getStartOfAnyPage(int pageNo, int pageSize) {
		int startIndex = (pageNo - 1) * pageSize + 1;
		if (startIndex < 1)
			startIndex = 1;
		return startIndex;
	}

	public <T extends BaseEntity> List<T> findAll(Class<T> clazz, String orderByPropertyName, String orderByType) {
		LinkedHashMap<String, String> orderInfos = new LinkedHashMap<String, String>();
		orderInfos.put(orderByPropertyName, orderByType);
		return this.findAll(clazz, orderInfos);
	}

	public <T extends BaseEntity> List<T> findByExample(T t, String orderByPropertyName, String orderByType) {
		LinkedHashMap<String, String> orderInfos = new LinkedHashMap<String, String>();
		orderInfos.put(orderByPropertyName, orderByType);
		return this.findByExample(t, orderInfos);
	}

	public <T extends BaseEntity> List<T> findByProperty(String propertyName, Object value, Class<T> clazz, String orderByPropertyName, String orderByType) {
		LinkedHashMap<String, String> orderInfos = new LinkedHashMap<String, String>();
		orderInfos.put(orderByPropertyName, orderByType);
		return this.findByProperty(propertyName, value, clazz, orderInfos);
	}

	public List<Object> findBySql(String sql) {
		return this.getSession().createSQLQuery(sql).list();
	}

	@Override
	public Criteria createCriteria(Class<? extends BaseEntity> clazz) throws Exception {
		// TODO Auto-generated method stub
		// 开启查询缓存
		Criteria criteria = this.getSession().createCriteria(clazz);
		criteria.setCacheable(true);
		// 关联查询时使用Distinct方法去除重复数据
		criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
		return criteria;
	}

	@Override
	public int deleteAll(Class<? extends BaseEntity> clazz) {
		Criteria criteria = this.getSession().createCriteria(clazz);
		List<BaseEntity> list = criteria.list();
		int count = list.size();
		for (BaseEntity base : list) {
			this.delete(base.getId(), clazz);
		}
		return count;
	}
}
